package net.lab1024.sa.admin.module.app.api.login;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
import lombok.extern.slf4j.Slf4j;
import net.lab1024.sa.admin.module.app.api.login.domain.AppLoginForm;
import net.lab1024.sa.admin.module.app.api.login.domain.AppLoginResultVO;
import net.lab1024.sa.admin.module.app.api.login.domain.RequestAppUser;
import net.lab1024.sa.admin.module.app.user.UserService;
import net.lab1024.sa.admin.module.app.user.domain.UserEntity;
import net.lab1024.sa.admin.module.business.oa.enterprise.EnterpriseService;
import net.lab1024.sa.admin.module.business.oa.enterprise.domain.vo.EnterpriseVO;
import net.lab1024.sa.base.common.code.UserErrorCode;
import net.lab1024.sa.base.common.constant.RequestHeaderConst;
import net.lab1024.sa.base.common.constant.StringConst;
import net.lab1024.sa.base.common.domain.RequestUser;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.enumeration.UserTypeEnum;
import net.lab1024.sa.base.common.util.SmartBeanUtil;
import net.lab1024.sa.base.common.util.SmartEnumUtil;
import net.lab1024.sa.base.common.util.SmartIpUtil;
import net.lab1024.sa.base.constant.LoginDeviceEnum;
import net.lab1024.sa.base.module.support.file.service.IFileStorageService;
import net.lab1024.sa.base.module.support.loginlog.LoginLogResultEnum;
import net.lab1024.sa.base.module.support.loginlog.LoginLogService;
import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogEntity;
import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogVO;
import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailEntity;
import net.lab1024.sa.base.module.support.securityprotect.service.ProtectLoginService;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.concurrent.ConcurrentMap;

@Slf4j
@Service
public class AppLoginService {

    /** 最大在线缓存人数 */
    private static final long CACHE_MAX_ONLINE_PERSON_COUNT = 10000L;

    /** 登录信息二级缓存 */
    private final ConcurrentMap<Long, RequestAppUser> loginEmployeeCache =
            new ConcurrentLinkedHashMap.Builder<Long, RequestAppUser>()
                    .maximumWeightedCapacity(CACHE_MAX_ONLINE_PERSON_COUNT)
                    .build();

    private final UserService userService;

    private final LoginLogService loginLogService;

    private final EnterpriseService enterpriseService;

    private final ProtectLoginService protectLoginService;

    private final IFileStorageService fileStorageService;

    public AppLoginService(UserService userService,
                           LoginLogService loginLogService,
                           EnterpriseService enterpriseService,
                           ProtectLoginService protectLoginService,
                           IFileStorageService fileStorageService) {
        this.userService = userService;
        this.loginLogService = loginLogService;
        this.enterpriseService = enterpriseService;
        this.protectLoginService = protectLoginService;
        this.fileStorageService = fileStorageService;
    }


    /**
     * 登录
     */
    public ResponseDTO<AppLoginResultVO> login(AppLoginForm form, String ip, String userAgent) {
        LoginDeviceEnum loginDeviceEnum = SmartEnumUtil.getEnumByValue(form.getLoginDevice(), LoginDeviceEnum.class);
        if (loginDeviceEnum == null) {
            return ResponseDTO.userErrorParam("登录设备暂不支持！");
        }

        UserEntity userEntity = userService.getByUserName(form.getUserName());
        if (userEntity == null) {
            return ResponseDTO.userErrorParam("登录名不存在！");
        }

        // 验证账号状态
        if (userEntity.getDisabledFlag()) {
            saveLoginLog(userEntity, ip, userAgent, "账号已禁用", LoginLogResultEnum.LOGIN_FAIL);
            return ResponseDTO.userErrorParam("您的账号已被禁用,请联系工作人员！");
        }

        // 按照等保登录要求，进行登录失败次数校验
        ResponseDTO<LoginFailEntity> loginFailEntityResponseDTO =
                protectLoginService.checkLogin(userEntity.getUserId(), UserTypeEnum.APP_USER);
        if (!loginFailEntityResponseDTO.getOk()) {
            return ResponseDTO.error(loginFailEntityResponseDTO);
        }

        // 密码错误
        if (!userEntity.getPassword().equals(UserService.getEncryptPwd(form.getPassword()))) {
            // 记录登录失败
            saveLoginLog(userEntity, ip, userAgent, "密码错误", LoginLogResultEnum.LOGIN_FAIL);
            // 记录等级保护次数
            String msg = protectLoginService.recordLoginFail(userEntity.getUserId(), UserTypeEnum.APP_USER,
                    userEntity.getUserName(), loginFailEntityResponseDTO.getData());
            return msg == null
                    ? ResponseDTO.userErrorParam("登录名或密码错误！")
                    : ResponseDTO.error(UserErrorCode.LOGIN_FAIL_WILL_LOCK, msg);
        }
        String saTokenLoginId = UserTypeEnum.APP_USER.getValue() + StringConst.COLON + userEntity.getUserId();
        // 登录
        StpUtil.login(saTokenLoginId, loginDeviceEnum.getDesc());

        // 获取用户信息
        RequestAppUser requestAppUser = loadLoginInfo(userEntity);

        // 放入缓存
        loginEmployeeCache.put(userEntity.getUserId(), requestAppUser);

        // 移除登录失败
        protectLoginService.removeLoginFail(userEntity.getUserId(), UserTypeEnum.APP_USER);

        // 获取登录结果信息
        AppLoginResultVO appLoginResultVO = getLoginResult(requestAppUser);

        //保存登录记录
        saveLoginLog(userEntity, ip, userAgent, loginDeviceEnum.getDesc(), LoginLogResultEnum.LOGIN_SUCCESS);

        // 设置 token
        appLoginResultVO.setToken(StpUtil.getTokenValue());

        return ResponseDTO.ok(appLoginResultVO);
    }

    /**
     * 获取登录结果
     */
    public AppLoginResultVO getLoginResult(RequestAppUser requestAppUser) {
        // copy基础信息
        AppLoginResultVO appLoginResultVO = SmartBeanUtil.copy(requestAppUser, AppLoginResultVO.class);

        // 上次登录信息
        LoginLogVO loginLogVO = loginLogService.queryLastByUserId(requestAppUser.getUserId(), UserTypeEnum.APP_USER, LoginLogResultEnum.LOGIN_SUCCESS);
        if (loginLogVO != null) {
            appLoginResultVO.setIp(loginLogVO.getLoginIp());
            appLoginResultVO.setLastLoginIpRegion(loginLogVO.getLoginIpRegion());
            appLoginResultVO.setLastLoginTime(loginLogVO.getCreateTime());
            appLoginResultVO.setUserAgent(loginLogVO.getUserAgent());
        }
        return appLoginResultVO;
    }

    /**
     * 加载登录信息
     */
    private RequestAppUser loadLoginInfo(UserEntity userEntity) {
        // copy基础信息
        RequestAppUser requestAppUser = SmartBeanUtil.copy(userEntity, RequestAppUser.class);

        // 补充其它信息
        // - 用户类型
        requestAppUser.setUserType(UserTypeEnum.APP_USER);

        // - 企业信息
        EnterpriseVO enterprise = enterpriseService.getDetail(userEntity.getEnterpriseId());
        if (enterprise != null) {
            requestAppUser.setEnterpriseName(enterprise.getEnterpriseName());
        }

        // 头像信息
        String avatar = userEntity.getAvatar();
        if(StringUtils.isNotBlank(avatar)){
            ResponseDTO<String> getFileUrl = fileStorageService.getFileUrl(avatar);
            if(BooleanUtils.isTrue(getFileUrl.getOk())){
                requestAppUser.setAvatar(getFileUrl.getData());
            }
        }
        return requestAppUser;
    }

    /**
     * 保存登录日志
     */
    private void saveLoginLog(UserEntity userEntity, String ip, String userAgent,
                              String remark, LoginLogResultEnum loginLogResultEnum) {
        LoginLogEntity loginEntity = LoginLogEntity.builder()
                .userId(userEntity.getUserId())
                .userType(UserTypeEnum.ADMIN_EMPLOYEE.getValue())
                .userName(userEntity.getNickName())
                .userAgent(userAgent)
                .loginIp(ip)
                .loginIpRegion(SmartIpUtil.getRegion(ip))
                .remark(remark)
                .loginResult(loginLogResultEnum.getValue())
                .createTime(LocalDateTime.now())
                .build();
        loginLogService.log(loginEntity);
    }

    /**
     * 根据登陆token 获取请求用户信息
     */
    public RequestAppUser getLoginUser(String loginId, HttpServletRequest request) {
        if (loginId == null) {
            return null;
        }

        Long requestUserId = getUserIdByLoginId(loginId);
        if (requestUserId == null) {
            return null;
        }

        RequestAppUser requestAppUser = loginEmployeeCache.get(requestUserId);
        if (requestAppUser == null) {
            // 员工基本信息
            UserEntity userEntity = userService.getById(requestUserId);
            if (userEntity == null) {
                return null;
            }

            requestAppUser = this.loadLoginInfo(userEntity);
            loginEmployeeCache.put(requestUserId, requestAppUser);
        }

        // 更新请求ip和user agent
        requestAppUser.setUserAgent(ServletUtil.getHeaderIgnoreCase(request, RequestHeaderConst.USER_AGENT));
        requestAppUser.setIp(ServletUtil.getClientIP(request));

        return requestAppUser;
    }

    /**
     * 根据登录ID获取用户ID
     */
    private Long getUserIdByLoginId(String loginId) {
        if (loginId == null) {
            return null;
        }
        try {
            String employeeIdStr = loginId.substring(2);
            return Long.parseLong(employeeIdStr);
        } catch (Exception e) {
            log.error("loginId parse error , loginId : {}", loginId, e);
            return null;
        }
    }

    /**
     * 退出登录
     */
    public ResponseDTO<String> logout(String token, RequestUser requestUser) {

        // sa token 登出
        StpUtil.logoutByTokenValue(token);

        // 清空登录信息缓存
        loginEmployeeCache.remove(requestUser.getUserId());

        //保存登出日志
        LoginLogEntity loginEntity = LoginLogEntity.builder()
                .userId(requestUser.getUserId())
                .userType(requestUser.getUserType().getValue())
                .userName(requestUser.getUserName())
                .userAgent(requestUser.getUserAgent())
                .loginIp(requestUser.getIp())
                .loginIpRegion(SmartIpUtil.getRegion(requestUser.getIp()))
                .loginResult(LoginLogResultEnum.LOGIN_OUT.getValue())
                .createTime(LocalDateTime.now())
                .build();
        loginLogService.log(loginEntity);

        return ResponseDTO.ok();
    }
}
